package com.example.demo;

import com.example.demo.AlgorithmDefine;
import com.example.demo.DecryptUtils;
import com.google.common.hash.Hashing;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;

/**
 * 加密工具类
 *
 * @author Liunh
 */
@Slf4j
public class EncryptUtils {

    private static String key = null;
    /**
     * 加盐
     */
    public static final String SALT = "salt";

    public EncryptUtils(String key) {
        this.key = key;
    }

    /**
     * ====================== md5加密 =========================
     */

    public static String md5(String input) {
        return DigestUtils.md5Hex(input.getBytes());
    }

    public static String md5(String input, String key) {
        return DigestUtils.md5Hex(new StringBuilder().append(input).append(key).toString().getBytes());
    }

    /**
     * MD5加密
     *
     * @param data
     * @return
     * @throws Exception
     */
    public static byte[] encryptMD5(byte[] data) throws Exception {
        MessageDigest md5 = MessageDigest.getInstance(AlgorithmDefine.ALGORITHM_MD5);
        md5.update(data);
        return md5.digest();
    }

    /**
     * 生成md5校验码
     *
     * @param srcContent 需要加密的数据
     * @return 加密后的md5校验码。出错则返回null。
     */
    public static String makeMd5Sum(byte[] srcContent) {
        if (srcContent == null) {
            return null;
        }
        String strDes = null;
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(srcContent);
            strDes = bytes2Hex(md5.digest()); // to HexString
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
        return strDes;
    }

    /**
     * 适用于上G大的文件
     *
     * @param file
     * @return
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    @SuppressWarnings("resource")
    public static String getFileMD5String(File file) throws IOException, NoSuchAlgorithmException {
        FileInputStream in = new FileInputStream(file);
        FileChannel ch = in.getChannel();
        MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
        MessageDigest messagedigest = MessageDigest.getInstance("MD5");
        messagedigest.update(byteBuffer);
        return bufferToHex(messagedigest.digest());
    }

    public static String getMD5String(String s) {
        String result = null;
        try {
            result = getMD5String(s.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage());
        }
        return result;
    }

    public static String getMD5String(byte[] bytes) throws NoSuchAlgorithmException {
        MessageDigest messagedigest = MessageDigest.getInstance("MD5");
        messagedigest.update(bytes);
        return bufferToHex(messagedigest.digest());
    }

    public static String computeContentMD5Header(InputStream inputStream) {
        // Consume the stream to compute the MD5 as a side effect.
        DigestInputStream s;
        try {
            s = new DigestInputStream(inputStream, MessageDigest.getInstance("MD5"));
            // drain the buffer, as the digest is computed as a side-effect
            byte[] buffer = new byte[8192];
            while (s.read(buffer) > 0) {
                ;
            }
            return new String(Base64.encodeBase64(s.getMessageDigest().digest()), StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 基本加密处理
     *
     * @param msg
     * @param type
     * @return
     */
    private static String encrypt(String msg, String type) {
        MessageDigest md;
        StringBuilder password = new StringBuilder();
        try {
            md = MessageDigest.getInstance("MD5");
            if (StringUtils.isNotBlank(type)) {
                md.update(type.getBytes());
            } else {
                md.update(msg.getBytes());
            }
            byte[] bytes = md.digest();
            for (int i = 0; i < bytes.length; i++) {
                String param = Integer.toString((bytes[i] & 0xff) + 0x100, 16);
                password.append(param.substring(1));
            }
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage());
        }
        return password.toString();
    }

    /** ========================================== SHA加密 ==================================================== */

    /**
     * SHA加密
     *
     * @param data
     * @return
     * @throws Exception
     */
    public static byte[] encryptSHA(byte[] data) throws Exception {
        MessageDigest sha = MessageDigest.getInstance(AlgorithmDefine.ALGORITHM_SHA1);
        sha.update(data);
        return sha.digest();
    }

    public static String sha(String input) {
        return DigestUtils.sha1Hex(input.getBytes());
    }

    public static String sha256(String input) {
        return DigestUtils.sha256Hex(input.getBytes());
    }

    public static byte[] encodeHmacSHA256(byte[] data, String key) throws Exception {
        SecretKey secretKey = new SecretKeySpec(key.getBytes(), AlgorithmDefine.ALGORITHM_HmacSHA);
        // 实例化Mac
        Mac mac = Mac.getInstance(secretKey.getAlgorithm());
        //初始化mac
        mac.init(secretKey);
        //执行消息摘要
        return mac.doFinal(data);
    }

    public static String sha384(String input) {
        return DigestUtils.sha384Hex(input.getBytes());
    }

    public static String sha512(String input) {
        return DigestUtils.sha512Hex(input.getBytes());
    }

    /**
     * 进行MD5加密
     */
    public static String passwordMd5(String password) {
        try {
            return Hashing.sha256().hashString(password, StandardCharsets.UTF_8).toString().toUpperCase();
        } catch (Exception e) {
            log.error("进行MD5加密发生异常.", e);
        }
        return null;
    }

    /**
     * ========================================== BASE64加密 ====================================================
     */

    public static String base64Encode(String input) {
        return Base64.encodeBase64String(input.getBytes()).trim();
    }

    /**
     * BASE64加密
     * <p>
     * 将二进制内容编码为base64字符串
     *
     * @param srcContent 需要编码的数据
     * @return String 编码结果。如果参数为null，则返回null。
     */
    public static String encodeBase64(byte[] srcContent) {
        if (srcContent == null) {
            return null;
        }
        String str = new String(new Base64().encode(srcContent));
        if (str.contains("\r\n")) {
            str = str.replace("\r\n", "");
        }
        return str;
    }




    /*public static String encode(String input) {
        long millis = System.currentTimeMillis();
        input = base64Encode(new StringBuilder().append(input).append(millis).toString());
        byte[] bytes = input.getBytes();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; ++i) {
            String s = Integer.toHexString(bytes[i] + 18);
            sb.append(new StringBuilder().append(s.charAt(1)).append("").append(s.charAt(0)).toString());
        }
        return SecurityUtil.reverse(sb.toString());
    }*/

    /**
     * ================= AES加密 =======================
     */

    public static String aesEenCode(String content) throws Exception {
        if (StringUtils.isEmpty(content)) {
            return content;
        }
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), AlgorithmDefine.ALGORITHM_AES);
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(1, keySpec);
        return Base64.encodeBase64String(cipher.doFinal(content.getBytes()));
    }

    public static String aesDecode(String content) throws Exception {
        if (StringUtils.isEmpty(content)) {
            return content;
        }
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), AlgorithmDefine.ALGORITHM_AES);
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(2, keySpec);
        return new String(cipher.doFinal(Base64.decodeBase64(content)));
    }


    /** ==================== DES加密 ============================ */

    /**
     * DES加密
     *
     * @param data
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] encryptDES(byte[] data, String key) throws Exception {
        Key k = toKey(DecryptUtils.decryptBASE64(key));
        Cipher cipher = Cipher.getInstance(AlgorithmDefine.ALGORITHM_DES);
        cipher.init(Cipher.ENCRYPT_MODE, k);
        return cipher.doFinal(data);
    }


    /**
     * ====================== Hex加密 =============================
     */

    private static String bufferToHex(byte bytes[]) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    private static String bufferToHex(byte bytes[], int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        int k = m + n;
        for (int l = m; l < k; l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
        // 默认的密码字符串组合，apache校验下载的文件的正确性用的就是默认的这个组合
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        char c0 = hexDigits[(bt & 0xf0) >> 4];
        char c1 = hexDigits[bt & 0xf];
        stringbuffer.append(c0);
        stringbuffer.append(c1);
    }

    /**
     * 盐值的原理非常简单，就是先把密码和盐值指定的内容合并在一起，再使用md5对合并后的内容进行演算，
     * 这样一来，就算密码是一个很常见的字符串，再加上用户名，最后算出来的md5值就没那么容易猜出来了。
     * 因为攻击者不知道盐值的值，也很难反算出密码原文。
     *
     * @param msg
     * @return
     */
    public static String encryptSalt(String msg) {
        return encrypt(msg, SALT);
    }

    /**
     * 先把密码和盐值指定的内容合并在一起，再使用sha1对合并后的内容进行演算，
     *
     * @return
     */
    public static String getSalt() {
        SecureRandom sr;
        byte[] salt = new byte[16];
        try {
            sr = SecureRandom.getInstance("SHA1PRNG", "SUN");
            sr.nextBytes(salt);
        } catch (Exception e) {
            log.error(e.getMessage());
        }

        return Arrays.toString(salt);
    }

    /**
     * 将生成的DES密钥转换为密钥对象
     *
     * @param key
     * @return
     * @throws Exception
     */
    public static Key toKey(byte[] key) throws Exception {
        DESKeySpec dks = new DESKeySpec(key);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(AlgorithmDefine.ALGORITHM_DES);
        return keyFactory.generateSecret(dks);
    }

    /**
     * 二进制转十六进制字符串
     */
    public static String bytes2Hex(byte bytes[]) {
        StringBuilder sb = new StringBuilder();
        String temp;
        for (byte b : bytes) {
            // 将每个字节与0xFF进行与运算，然后转化为10进制，然后借助于Integer再转化为16进制
            temp = Integer.toHexString(0xFF & b);
            // 每个字节8为，转为16进制标志，2个16进制位
            if (temp.length() == 1) {
                temp = "0" + temp;
            }
            sb.append(temp);
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        String sha256 = EncryptUtils.sha256("123456");
        System.out.println(sha256);
    }
}